/*
 * Anything: A flexible and easy to use data structure that combines the 
 * characteristics of a list and a hashmap into one data structure.
 * 
 * Based on a paper by Peter Sommerlad and Marcel Rueedi on 
 * 'Do-it-yourself reflection' entered at EuroPLOP 1998.
 * 
 * This library was written completely from scratch and is based only 
 * in spirit on the original Java Anything implementation written by 
 * Andre Weinand/Marcel Rueedi in 1997/99 and enhanced by Stefan Tramm 
 * in 2006.
 * 
 * Copyright (c) 2008 Thomas Marti and others. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package any.functor.impl.func;

import static any.functor.Functions.asIterable;
import static any.functor.Functions.asList;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import any.ImmutableAny;
import any.functor.interfaces.AnyIteratingFunction;
import any.functor.interfaces.AsArray;
import any.functor.interfaces.Function;
import any.functor.interfaces.FunctionTypedPredicate;
import any.functor.interfaces.PrimitiveTargetTypedFunction;
import any.functor.interfaces.TargetTypedFunction;

public class AsArrayImpl<T> 
	extends BaseAnyIteratingFunction<T, Void, T, AsArrayImpl<T>> 
	implements AsArray<T> {

    private static final Map<Class<?>, Class<?>> wrapperToPrimitive = new HashMap<Class<?>, Class<?>>();
    static {
         wrapperToPrimitive.put(Boolean.class, boolean.class);
         wrapperToPrimitive.put(Byte.class, byte.class);
         wrapperToPrimitive.put(Character.class, char.class);
         wrapperToPrimitive.put(Short.class, short.class);
         wrapperToPrimitive.put(Integer.class, int.class);
         wrapperToPrimitive.put(Long.class, long.class);
         wrapperToPrimitive.put(Double.class, double.class);
         wrapperToPrimitive.put(Float.class, float.class);
    }
	
//	private Function<ImmutableAny, ?> asFunc;

	private final boolean asPrimitive;

	public AsArrayImpl(boolean asPrimitive) {
		this.asPrimitive = asPrimitive;
	}

	@SuppressWarnings("unchecked")
	public T apply(ImmutableAny from) {
		Class<?> type = getArrayType();
		
		Object array;
		Iterable<?> elements;

		if (!from.onlySequentialAccess() && predicate == null) {
			//TODO: sublist fehlt...from.iterable() verwenden??
			elements = from.give(asIterable().eachElement(asFunc).order(direction));
			array = Array.newInstance(type, from.size());
		} else {
			elements = from.give(asList().eachElement(asFunc).select(predicate).order(direction));
			array = Array.newInstance(type, ((List<?>)elements).size());
		}
		
		int i = 0;
		for (Object next : elements) {
			Array.set(array, i++, next);
		}
		
		return (T)array;
	}

	public Class<?> getArrayType() {
		Class<?> type;
		if (asPrimitive) {
			//TODO: Wirft NPE, wenn asFunc noch nicht gesetzt!
			Class<?> targetType = ((PrimitiveTargetTypedFunction<?, ?>)asFunc).getTargetType();
			type = wrapperToPrimitive.get(targetType);
			if (type == null) {
				throw new IllegalStateException(targetType + " has no corresponding primitive type!");
			}
		} else if (asFunc == null || !(asFunc instanceof TargetTypedFunction<?, ?>) || ((TargetTypedFunction<?, ?>)asFunc).getTargetType() == null) {
			type = Object.class;
		} else {
			type = ((TargetTypedFunction<?, ?>)asFunc).getTargetType();
		}
		return type;
	}

	@SuppressWarnings("unchecked")
	public <X> AnyIteratingFunction<X[]> eachElement(Function<ImmutableAny, X> func) {
		this.asFunc = func;
		return (AnyIteratingFunction<X[]>)this;
	}

	@SuppressWarnings("unchecked")
	public <X> AnyIteratingFunction<X[]> select(FunctionTypedPredicate<X> predicate) {
		this.asFunc = predicate.getAsFunction();
		super.select(predicate);
		return (AnyIteratingFunction<X[]>)this;
	}

	@Override
	protected String toStringPrefix() {
		return "As" + (asPrimitive ? "Primitive": "") + "Array of " + getArrayType().getSimpleName();
	}
	
}
